﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jayden.Dll.Cards.Poker.Enums;
using System.Threading;

namespace Jayden.Dll.Cards.Poker
{
	public class PokerTable
	{
		private string m_TableName;
		private bool m_Closed = false;
		private int m_Button = 0;
		private PokerDealer m_Dealer;
		private PokerGameConfiguration m_Configuration;
		private PokerTableSeat[] m_Seats;

		private HashSet<PokerSocket> m_Observers = new HashSet<PokerSocket>();

		public PokerTable(string tableName, PokerGameConfiguration config)
		{
			m_TableName = tableName;
			m_Configuration = config;
			m_Seats = new PokerTableSeat[m_Configuration.NumberOfSeats];
			for (int c = 0; c < m_Configuration.NumberOfSeats; c++)
				m_Seats[c] = new PokerTableSeat(this, c);
		}

		public bool Open { get { return !m_Closed; } }
		public bool Closed { get { return m_Closed; } }
		public string TableName { get { return m_TableName; } }
		public int NumberOfSeats { get { return m_Seats.Length; } }
		public int NumberOfPlayers { get { return OccupiedSeats.Count(); } }
		public PokerGameConfiguration Configuration { get { return m_Configuration; } }
		public PokerType Type { get { return m_Configuration.Type; } }
		public PokerLimit Limit { get { return m_Configuration.Limit; } }
		public PokerCurrency Currency { get { return m_Configuration.Currency; } }
		public ulong Ante { get { return m_Configuration.GetAnte(Dealer); } }
		public ulong SmallBlind { get { return m_Configuration.GetSmallBlind(Dealer); } }
		public ulong BigBlind { get { return m_Configuration.GetBigBlind(Dealer); } }

		public PokerDealer Dealer
		{
			get { return m_Dealer; }
			set
			{
				if (m_Dealer == value)
					return;
				if (m_Dealer != null)
					m_Dealer.Table = null;
				m_Dealer = value;
				if (m_Dealer != null)
					m_Dealer.Table = this;
			}
		}

		public int Button { get { return m_Button; } set { m_Button = value; } }
		public PokerTableSeat this[int index] { get { return m_Seats[index]; } }
		public PokerTableSeat this[string player]
		{
			get
			{
				foreach (PokerTableSeat seat in Seats)
					if (seat.Player != null && seat.Player.Login == player)
						return seat;
				return null;
			}
		}
		public PokerTableSeat this[PokerSocket socket]
		{
			get
			{
				foreach (PokerTableSeat seat in Seats)
					if (seat.Player != null && seat.Player.Socket == socket)
						return seat;
				return null;
			}
		}

		public void Sit(int seatIndex, PokerPlayer player, ulong amount)
		{
			lock (m_Seats)
			{
				PokerTableSeat seat = this[seatIndex];
				if (seat.Player != null)
					throw new Exception("Seat already taken.");
				seat.Player = player;
				seat.Stack = amount;
				seat.State = PokerSeatState.Waiting;
				player.AddSeat(seat);
				if (player.Socket != null)
					AddObserver(player.Socket);
				BroadCastMessage(string.Format("{0} joined the table with {1} on seat {2}.", player.Login, Currency.ToString(amount), seatIndex));
			}
		}
		public void ReBuy(int seatIndex, ulong amount)
		{
			PokerTableSeat seat = this[seatIndex];
			if (seat.Player == null)
				throw new Exception("Seat empty.");
			seat.Stack += amount;
			if(seat.State == PokerSeatState.SittingOut)
				seat.State = PokerSeatState.Waiting;
			BroadCastMessage(string.Format("{0} re-buy for {1}.", seat.Player.Login, Currency.ToString(amount)));
		}
		public void Leave(int seatIndex, bool force = false)
		{
			lock (m_Seats)
			{
				PokerTableSeat seat = this[seatIndex];
				if (seat.State == PokerSeatState.Playing && Dealer.Busy)
				{
					seat.State = PokerSeatState.Disconnected;
					BroadCastMessage(string.Format("{0} will leave the table with {1} when the hand is over.", seat.Player.Login, Currency.ToString(seat.Stack)));
				}
				else
				{
					if (Currency == seat.Player.Currency)
						seat.Player.BankRoll += seat.Stack;
					string login = seat.Player.Login;
					ulong stack = seat.Stack;
					seat.Player.RemoveSeat(seat);
					seat.Player = null;
					seat.State = PokerSeatState.Empty;
					seat.Stack = 0;
					BroadCastMessage(string.Format("{0} leave the table with {1}.", login, Currency.ToString(stack)));
				}
			}
		}

		public bool AddObserver(PokerSocket socket)
		{
			lock (m_Observers)
			{
				return m_Observers.Add(socket);
			}
		}
		public void RemoveObserver(PokerSocket socket)
		{
			lock (m_Observers)
			{
				m_Observers.Remove(socket);
			}
		}

		public delegate void PokerSocketDelegate(PokerSocket socket);
		public void BroadCast(PokerSocketDelegate func)
		{
			lock(m_Observers)
			{
				List<PokerSocket> toRemove = new List<PokerSocket>();
				foreach(PokerSocket observer in m_Observers)
					if (observer.Connected)
					{
						lock (observer)
						{
							try
							{
								func(observer);
							}
							catch(Exception e)
							{
								toRemove.Add(observer);
							}
						}
					}
				foreach (PokerSocket socket in toRemove)
					m_Observers.Remove(socket);
			}
		}
		public void ShowCards(params Card[] cards)
		{
			PokerSocketDelegate showCardsFunc = delegate(PokerSocket socket)
			{
				socket.WriteInt32((int)PokerProtocolMessageType.DealerShowCards);
				socket.WriteString(TableName);
				socket.WriteByte((byte)cards.Length);
				for (int c = 0; c < cards.Length; c++)
					socket.WriteCard(cards[c]);
			};
			BroadCast(showCardsFunc);

			BroadCastInitTable();
		}
		public void BroadCastChatMessage(string playerName, string message)
		{
			PokerSocketDelegate chatMessageFunc = delegate(PokerSocket socket)
			{
				socket.WriteInt32((int)PokerProtocolMessageType.ChatMessage);
				socket.WriteString(TableName);
				socket.WriteString(playerName);
				socket.WriteString(message);
			};
			BroadCast(chatMessageFunc);
		}
		public void BroadCastMessage(string message, bool initTable = true)
		{
			message = "(" + m_TableName + ") " + message;
			PokerSocketDelegate messageFunc = delegate(PokerSocket socket) { socket.SendMessage(message); };
			BroadCast(messageFunc);
			if(initTable)
				BroadCastInitTable();
		}
		public void BroadCastAction(string playerName, PokerActionType action, ulong amount)
		{
			PokerSocketDelegate actionFunc = delegate(PokerSocket socket)
			{
				socket.WriteInt32((int)PokerProtocolMessageType.ActionDone);
				socket.WriteString(TableName);
				socket.WriteString(playerName);
				socket.WriteByte((byte)action);
				socket.WriteUInt64(amount);
			};
			BroadCast(actionFunc);
		}
		public void BroadCastTimer(int seatIndex, int time)
		{
			PokerSocketDelegate timerFunc = delegate(PokerSocket socket)
			{
				socket.WriteInt32((int)PokerProtocolMessageType.Timer);
				socket.WriteString(TableName);
				socket.WriteInt32(seatIndex);
				socket.WriteInt32(time);
			};
			BroadCast(timerFunc);
		}
		public void BroadCastInitTable()
		{
			BroadCast(InitTable);
		}
		public void InitTable(PokerSocket socket)
		{
			PokerTableSeat me = this[socket];
			socket.WriteInt32((int)PokerProtocolMessageType.InitTable);
			if (Configuration.IsTournament)
				socket.WriteString((Configuration as PokerTournamentConfiguration).TournamentName);
			else
				socket.WriteString(string.Empty);
			socket.WriteString(TableName);
			socket.WriteString(Currency.IsoCode);
			socket.WriteUInt64(Ante);
			socket.WriteUInt64(SmallBlind);
			socket.WriteUInt64(BigBlind);
			socket.WriteByte((byte)Button);
			socket.WriteByte((byte)Type);
			socket.WriteByte((byte)Limit);
			// texasholdem dealer part
			TexasHoldemDealer dealer = Dealer as TexasHoldemDealer;
			socket.WriteByte((byte)dealer.Street);
			socket.WriteByte(dealer.CurrentAction);
			socket.WriteByte((byte)dealer.CommunityCards.Count);
			foreach (Card card in dealer.CommunityCards)
				socket.WriteCard(card);
			if (dealer.Pot == null)
				socket.WriteUInt64(0);
			else
				socket.WriteUInt64(dealer.Pot.TotalAmount);
			socket.WriteByte((byte)dealer.Street);
			socket.WriteByte((byte)NumberOfSeats);
			List<PokerTableSeat> occupied = new List<PokerTableSeat>(OccupiedSeats);
			socket.WriteByte((byte)occupied.Count);
			foreach (PokerTableSeat seat in occupied)
			{
				socket.WriteByte((byte)seat.Index);
				socket.WriteString(seat.Player.Login);
				socket.WriteUInt64(seat.Stack);
				socket.WriteByte((byte)seat.State);
				// texasholdem dealer part
				socket.WriteUInt64(dealer.Bets[seat.Index]);
				CardList closeCards = dealer.CloseCards[seat.Index];
				if (closeCards == null)
					socket.WriteByte(0);
				else
				{
					socket.WriteByte((byte)closeCards.Count);
					foreach (Card card in closeCards)
						if(!dealer.Visible.Contains(card) && (me == null || me != seat))
							socket.WriteCard(null);
						else
							socket.WriteCard(card);
				}
			}
			if(dealer.PlayersLeftToTalk == null)
				socket.WriteByte(0);
			else
			{
				socket.WriteByte((byte)dealer.PlayersLeftToTalk.Count);
				foreach (PokerTableSeat leftToTalk in dealer.PlayersLeftToTalk)
					socket.WriteByte((byte)leftToTalk.Index);
			}
		}

		public IEnumerable<PokerTableSeat> Seats { get { return GetSeatsFrom(m_Button + 1); } }

		public IEnumerable<PokerTableSeat> GetSeatsFrom(int first)
		{
			for (int c = 0; c < NumberOfSeats; c++)
			{
				int index = (c + first) % NumberOfSeats;
				PokerTableSeat seat = this[index];
				yield return seat;
			}
		}

		public IEnumerable<PokerTableSeat> OccupiedSeats
		{
			get
			{
				foreach (PokerTableSeat seat in Seats)
				{
					if (!(seat.State == PokerSeatState.Empty || seat.State == PokerSeatState.Reserved))
						yield return seat;
				}
			}
		}

		public IEnumerable<PokerTableSeat> PlayingSeats
		{
			get
			{
				foreach (PokerTableSeat seat in Seats)
				{
					if (seat.State == PokerSeatState.Playing)
						yield return seat;
				}
			}
		}


		public void Stop()
		{
			m_Closed = true;
		}

		public void BroadCastBlindGoesUp(ulong smallBlind, ulong bigBlind, ulong ante)
		{
			PokerSocketDelegate blindGoesUpFunc = delegate(PokerSocket socket)
			{
				socket.WriteInt32((int)PokerProtocolMessageType.BlindGoesUp);
				socket.WriteString(TableName);
				socket.WriteUInt64(smallBlind);
				socket.WriteUInt64(bigBlind);
				socket.WriteUInt64(ante);
			};
			BroadCast(blindGoesUpFunc);

			if (ante == 0)
				BroadCastMessage(string.Format("Blinds are going up, blinds {0}/{1}.", Currency.ToString(smallBlind), Currency.ToString(bigBlind)), false);
			else
				BroadCastMessage(string.Format("Blinds are going up, blinds {0}/{1} ante {2}.", Currency.ToString(smallBlind), Currency.ToString(bigBlind), Currency.ToString(ante)), false);

		}

		public int FirstEmptySeat
		{
			get
			{
				foreach (PokerTableSeat seat in Seats)
					if (seat.State == PokerSeatState.Empty)
						return seat.Index;
				return -1;
			}
		}
	}
}
